/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston MA 02111-1307, USA.
 * 
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 * 
 * http://www.sgi.com 
 * 
 * For further information regarding this notice, see: 
 * 
 * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 */
/*
 * iogen - a tool for generating file/sds io for a doio process
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#ifdef CRAY
#include <sys/file.h>
#include <sys/iosw.h>
#include <sys/listio.h>
#endif
#ifdef sgi
#include <sys/statvfs.h>
#include <sys/fs/xfs_itable.h>
#endif

#ifdef CRAY
#include "libkern.h"
#endif
#include "doio.h"
#include "str_to_bytes.h"
#include "string_to_tokens.h"
#include "open_flags.h"
#include "random_range.h"

#ifndef PATH_MAX
#define	PATH_MAX 512	/* ??? */
#endif

#ifndef BSIZE
#ifdef linux
#define BSIZE DEV_BSIZE
#else
#define BSIZE 512
#endif
#endif

#define RAW_IO(_flags_)	((_flags_) & (O_RAW | O_SSD))

#ifndef __linux__
extern char 	*sys_errlist[];
#endif
#define SYSERR	strerror(errno)

/*
 * Structure for retaining test file information
 */

struct file_info {
	char	f_path[MAX_FNAME_LENGTH+1]; /* file name (full path)	*/
	int	f_length;	/* length in bytes	    	    	*/
	int	f_iou;		/* file iounit  	    	    	*/
	int	f_riou;		/* file raw iounit (for O_RAW/O_SSD)    */
	int	f_dalign;	/* direct I/O alignment                 */
	int	f_nextoff;	/* offset of end of last io operation   */
	int	f_type; 	/* file type S_IFREG, etc...		*/
	int	f_lastoffset;   /* offset of last io operation  	*/
	int	f_lastlength;   /* length of last io operation   	*/
};

/*
 * Simple structure for associating strings with values - useful for converting
 * cmdline args to internal values, as well as printing internal values in
 * a human readable form.
 */

struct strmap {
	char	*m_string;
	int	m_value;
	int	m_flags;
};

/*
 * Declare cmdline option flags/variables initialized in parse_cmdline()
 */

#define OPTS	"a:dhf:i:L:m:op:qr:s:t:T:O:N:"

int	a_opt = 0;		/* async io comp. types supplied	    */
int 	o_opt = 0;		/* form overlapping requests	    	    */
int 	f_opt = 0;		/* test flags   	    	    	    */
int 	i_opt = 0;		/* iterations - 0 implies infinite	    */
int	L_opt = 0;		/* listio min-max nstrides & nents	    */
int 	m_opt = 0;		/* offset mode	    	    	    	    */
int	O_opt = 0;		/* file creation Open flags		    */
int 	p_opt = 0;		/* output pipe - default is stdout	    */
int 	r_opt = 0;		/* specify raw io multiple instead of	    */
				/* getting it from the mounted on device.   */
				/* Only applies to regular files.   	    */
int 	s_opt = 0;		/* syscalls	    	    	    	    */
int 	t_opt = 0;		/* min transfer size (bytes)    	    */
int 	T_opt = 0;		/* max transfer size (bytes)    	    */
int 	q_opt = 0;		/* quiet operation on startup   	    */
char	TagName[40];		/* name of this iogen (see Monster)	    */
struct	strmap *Offset_Mode;	/* M_SEQUENTIAL, M_RANDOM, etc. 	    */
int 	Iterations;		/* # requests to generate (0 --> infinite)  */
int 	Time_Mode = 0;		/* non-zero if Iterations is in seconds	    */
				/* (ie. -i arg was suffixed with 's')       */
char	*Outpipe;		/* Pipe to write output to if p_opt 	    */
int 	Mintrans;		/* min io transfer size	    	    	    */
int 	Maxtrans;		/* max io transfer size	    	    	    */
int 	Rawmult;		/* raw/ssd io multiple (from -r)    	    */
int	Minstrides;		/* min # of listio strides per request	    */
int	Maxstrides;		/* max # of listio strides per request	    */
int	Oflags;			/* open(2) flags for creating files	    */
int	Ocbits;			/* open(2) cbits for creating files	    */
int	Ocblks;			/* open(2) cblks for creating files	    */
int	Orealtime=0;		/* flag set for -O REALTIME		    */
int	Oextsize=0;		/* real-time extent size		    */
int	Oreserve=1;		/* flag for -O [no]reserve		    */
int	Oallocate=0;		/* flag for -O allocate			    */
int	Owrite=1;		/* flag for -O nowrite			    */

int	Nfiles = 0;		/* # files on cmdline			    */
struct	file_info *File_List;	/* info about each file	    		    */
int	Nflags = 0;		/* # flags on cmdline			    */
struct	strmap *Flag_List[128];	/* flags selected from cmdline		    */
int	Nsyscalls = 0;		/* # syscalls on cmdline		    */
struct	strmap *Syscall_List[128]; /* syscalls selected on cmdline	    */
int	Fileio = 0;		/* flag indicating that a file		    */
				/* io syscall has been chosen.	 	    */
int	Naio_Strat_Types = 0;	/* # async io completion types		    */
struct	strmap *Aio_Strat_List[128]; /* Async io completion types	    */

void	startup_info();

/*
 * Map async io completion modes (-a args) names to values.  Macros are
 * defined in doio.h.
 */

struct strmap	Aio_Strat_Map[] = {
#ifndef linux
	{ "poll",	A_POLL		},
	{ "signal",	A_SIGNAL	},
#else
	{ "none",	0	},
#endif /* !linux */
#ifdef CRAY
#if _UMK || RELEASE_LEVEL >= 8000
	{ "recall",	A_RECALL	},
#endif

#ifdef RECALL_SIZEOF
	{ "recalla",    A_RECALLA	},
#endif
	{ "recalls",    A_RECALLS	},
#endif /* CRAY */

#ifdef sgi
	{ "suspend",	A_SUSPEND	},
	{ "callback",	A_CALLBACK	},
#endif
	{ NULL,		-1		}
};

/*
 * Offset_Mode #defines
 */

#define M_RANDOM    	1
#define M_SEQUENTIAL	2
#define M_REVERSE   	3

/*
 * Map offset mode (-m args) names to values
 */

struct strmap	Omode_Map[] = {
	{ "random",		M_RANDOM	},
	{ "sequential",		M_SEQUENTIAL    },
	{ "reverse",		M_REVERSE   	},
	{ NULL,			-1	    	}
};

/*
 * Map syscall names (-s args) to values - macros are defined in doio.h.
 */
#define	SY_ASYNC	00001
#define	SY_WRITE	00002
#define	SY_SDS		00010
#define	SY_LISTIO	00020
#define	SY_NENT		00100	/* multi entry vs multi stride >>> */

struct strmap	Syscall_Map[] = {
	{ "read",		READ,		0			},
	{ "write",		WRITE,		SY_WRITE		},
#ifdef CRAY
	{ "reada",		READA,		SY_ASYNC		},
	{ "writea",		WRITEA,		SY_WRITE|SY_ASYNC	},
#ifndef _CRAYMPP
	{ "ssread",		SSREAD,		SY_SDS			},
	{ "sswrite",		SSWRITE,	SY_WRITE|SY_SDS		},
#endif
	{ "listio",		LISTIO,		SY_ASYNC		},

	/* listio as 4 system calls */
	{ "lread",		LREAD,		0			},
	{ "lreada",		LREADA,		SY_ASYNC		},
	{ "lwrite",		LWRITE,		SY_WRITE		},
	{ "lwritea",		LWRITEA,	SY_WRITE|SY_ASYNC	},

	/* listio with nstrides > 1 */
	{ "lsread",		LSREAD,		0			},
	{ "lsreada",		LSREADA,	SY_ASYNC		},
	{ "lswrite",		LSWRITE,	SY_WRITE		},
	{ "lswritea",		LSWRITEA,	SY_WRITE|SY_ASYNC	},

	/* listio with nents > 1 */
	{ "leread",		LEREAD,		0|SY_NENT		},
	{ "lereada",		LEREADA,	SY_ASYNC|SY_NENT	},
	{ "lewrite",		LEWRITE,	SY_WRITE|SY_NENT	},
	{ "lewritea",		LEWRITEA,	SY_WRITE|SY_ASYNC|SY_NENT },

	/* listio with nents > 1 & nstrides > 1 */

	/* all listio system calls under one name */
	{ "listio+",		LREAD,		0			},
	{ "listio+",		LREADA,		SY_ASYNC		},
	{ "listio+",		LWRITE,		SY_WRITE		},
	{ "listio+",		LWRITEA,	SY_WRITE|SY_ASYNC	},
	{ "listio+",		LSREAD,		0			},
	{ "listio+",		LSREADA,	SY_ASYNC		},
	{ "listio+",		LSWRITE,	SY_WRITE		},
	{ "listio+",		LSWRITEA,	SY_WRITE|SY_ASYNC	},
	{ "listio+",		LEREAD,		0|SY_NENT		},
	{ "listio+",		LEREADA,	SY_ASYNC|SY_NENT	},
	{ "listio+",		LEWRITE,	SY_WRITE|SY_NENT	},
	{ "listio+",		LEWRITEA,	SY_WRITE|SY_ASYNC|SY_NENT },
#endif

#ifdef sgi
	{ "pread",		PREAD   				},
	{ "pwrite",		PWRITE,		SY_WRITE		},
	{ "aread",		AREAD,		SY_ASYNC		},
	{ "awrite",		AWRITE,		SY_WRITE|SY_ASYNC	},
#if 0
	/* not written yet */
	{ "llread",		LLREAD,		0			},
	{ "llaread",		LLAREAD,	SY_ASYNC		},
	{ "llwrite",		LLWRITE,	0			},
	{ "llawrite",		LLAWRITE,	SY_ASYNC		},
#endif
	{ "resvsp",		RESVSP, 	SY_WRITE		},
	{ "unresvsp",		UNRESVSP, 	SY_WRITE		},
	{ "reserve",		RESVSP, 	SY_WRITE		},
	{ "unreserve",		UNRESVSP, 	SY_WRITE		},
	{ "ffsync",		DFFSYNC, 	SY_WRITE		},
#endif /* SGI */

#ifndef CRAY
	{ "readv",		READV					},
	{ "writev",		WRITEV,		SY_WRITE		},
	{ "mmread",		MMAPR					},
	{ "mmwrite",		MMAPW,		SY_WRITE		},
	{ "fsync2",		FSYNC2, 	SY_WRITE		},
	{ "fdatasync",		FDATASYNC, 	SY_WRITE		},
#endif

	{ NULL,			-1      }
};

/*
 * Map open flags (-f args) to values
 */
#define	FLG_RAW		00001

struct strmap	Flag_Map[] = {
	{ "buffered",		0,			0	},
	{ "sync",		O_SYNC,			0	},
#ifdef CRAY
	{ "raw",		O_RAW,			FLG_RAW	},
	{ "raw+wf",		O_RAW | O_WELLFORMED,	FLG_RAW	},
	{ "raw+wf+ldraw",	O_RAW | O_WELLFORMED | O_LDRAW,	FLG_RAW	},
	{ "raw+wf+ldraw+sync",	O_RAW | O_WELLFORMED | O_LDRAW | O_SYNC, FLG_RAW },
#ifdef O_SSD
	{ "ssd",		O_SSD,			FLG_RAW	},
#endif
#ifdef O_LDRAW
	{ "ldraw",		O_LDRAW,		0	},
#endif
#ifdef O_PARALLEL
	{ "parallel",		O_PARALLEL | O_RAW | O_WELLFORMED,
		  FLG_RAW },
	{ "parallel+sync",	O_PARALLEL | O_RAW | O_WELLFORMED | O_SYNC,
		  FLG_RAW },
	{ "parallel+ldraw",	O_PARALLEL | O_RAW | O_WELLFORMED | O_LDRAW,
		  FLG_RAW },
	{ "parallel+ldraw+sync",
		  O_PARALLEL | O_RAW | O_WELLFORMED | O_LDRAW | O_SYNC,
		  FLG_RAW },
#endif
#endif /* CRAY */

#ifdef sgi
	{ "direct",		O_DIRECT,		FLG_RAW },
	{ "dsync",		O_DSYNC		},	/* affects writes */
	{ "rsync",		O_RSYNC		},	/* affects reads */
	{ "rsync+dsync",	O_RSYNC|O_DSYNC	},
#endif
	{ NULL, 	    -1	    	}
};

/*
 * Map file types to strings
 */

struct strmap	Ftype_Map[] = {
	{ "regular",	S_IFREG },
	{ "blk-spec",	S_IFBLK },
	{ "chr-spec",	S_IFCHR },
	{ NULL,		0 }
};

/*
 * Misc declarations
 */

int		Sds_Avail;

char	Byte_Patterns[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
			      'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
			      'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
			      'Y', 'Z' };


int form_iorequest(struct io_req *);
int init_output();
int parse_cmdline(int argc, char **argv, char *opts);
int help(FILE *stream);
int usage(FILE *stream);


int
main(argc, argv)
int 	argc;
char	**argv;
{
    int	    	    rseed, outfd, infinite;
    time_t  	    start_time;
    struct io_req   req;
    
    umask(0);

#ifdef CRAY
    Sds_Avail = sysconf(_SC_CRAY_SDS);
#else
    Sds_Avail = 0;
#endif

    TagName[0] = '\0';
    parse_cmdline(argc, argv, OPTS);

    /*
     * Initialize output descriptor.  
     */
    if (! p_opt) {
	outfd = 1;
    } else {
	outfd = init_output();
    }

    rseed = getpid();
    random_range_seed(rseed);       /* initialize random number generator */

    /*
     * Print out startup information, unless we're running in quiet mode
     */
    if (!q_opt)
	startup_info(stderr, rseed);
	{
    	struct timeval ts;
		gettimeofday(&ts,NULL);
		start_time = ts.tv_sec;
	}
    /*
     * While iterations (or forever if Iterations == 0) - compute an
     * io request, and write the structure to the output descriptor
     */

    infinite = !Iterations;
	struct timeval ts;
	gettimeofday(&ts,NULL);
    while (infinite ||
	   (! Time_Mode && Iterations--) ||
	   (Time_Mode && (ts.tv_sec - start_time <= Iterations))) {
	gettimeofday(&ts,NULL);
	memset(&req, 0, sizeof(struct io_req));
	if (form_iorequest(&req) == -1) {
	    fprintf(stderr, "iogen%s:  form_iorequest() failed\n", TagName);
	    continue;
	}

	req.r_magic = DOIO_MAGIC;
	if (write(outfd, (char *)&req, sizeof(req)) == -1)
		perror("Warning: Could not write");
    }

    exit(0);

}   /* main */

void
startup_info(FILE *stream, int seed)
{
    char	*value_to_string(), *type;
    int		i;

    fprintf(stream, "\n");
    fprintf(stream, "iogen%s starting up with the following:\n", TagName);
    fprintf(stream, "\n");

    fprintf(stream, "Out-pipe:              %s\n", 
	    p_opt ? Outpipe : "stdout");

    if (Iterations) {
	fprintf(stream, "Iterations:            %d", Iterations);
	if (Time_Mode)
	    fprintf(stream, " seconds");

	fprintf(stream, "\n");
    } else {
	fprintf(stream, "Iterations:            Infinite\n");
    }

    fprintf(stream,
	    "Seed:                  %d\n", seed);

    fprintf(stream,
	    "Offset-Mode:           %s\n", Offset_Mode->m_string);

    fprintf(stream, "Overlap Flag:          %s\n",
	    o_opt ? "on" : "off");

    fprintf(stream,
	    "Mintrans:              %-11d (%d blocks)\n",
	    Mintrans, (Mintrans+BSIZE-1)/BSIZE);

    fprintf(stream,
	    "Maxtrans:              %-11d (%d blocks)\n",
	    Maxtrans, (Maxtrans+BSIZE-1)/BSIZE);

    if (! r_opt) 
	fprintf(stream,
		"O_RAW/O_SSD Multiple:  (Determined by device)\n");
    else
	fprintf(stream,
		"O_RAW/O_SSD Multiple:  %-11d (%d blocks)\n",
		Rawmult, (Rawmult+BSIZE-1)/BSIZE);

    fprintf(stream, "Syscalls:              ");
    for (i = 0; i < Nsyscalls; i++)
	fprintf(stream,
		"%s ", Syscall_List[i]->m_string);
    fprintf(stream, "\n");

    fprintf(stream, "Aio completion types:  ");
    for (i = 0; i < Naio_Strat_Types; i++)
	fprintf(stream,
		"%s ", Aio_Strat_List[i]->m_string);
    fprintf(stream, "\n");

    if (Fileio) {
	fprintf(stream, "Flags:                 ");
	for (i = 0; i < Nflags; i++)
	    fprintf(stream,
		    "%s ", Flag_List[i]->m_string);

	fprintf(stream, "\n");
	fprintf(stream, "\n");
	fprintf(stream, "Test Files:  \n");
	fprintf(stream, "\n");
	fprintf(stream,
		"Path                                          Length    iou   raw iou file\n");
	fprintf(stream,
		"                                              (bytes) (bytes) (bytes) type\n"); 
	fprintf(stream,
		"-----------------------------------------------------------------------------\n");

	for (i = 0; i < Nfiles; i++) {
	    type = value_to_string(Ftype_Map, File_List[i].f_type);
	    fprintf(stream, "%-40s %12d %7d %7d %s\n",
		    File_List[i].f_path, File_List[i].f_length,
		    File_List[i].f_iou, File_List[i].f_riou, type);
	}
    }
}

/*
 * Initialize output descriptor.  If going to stdout, its easy,
 * otherwise, attempt to create a FIFO on path Outpipe.  Exit with an
 * error code if this cannot be done.
 */
int
init_output()
{
    int	 outfd;
    struct stat	    sbuf;

    if (stat(Outpipe, &sbuf) == -1) {
	if (errno == ENOENT) {
	    if (mkfifo(Outpipe, 0666) == -1) {
		fprintf(stderr, "iogen%s:  Could not mkfifo %s:  %s\n",
			TagName, Outpipe, SYSERR);
		exit(2);
	    }
	} else {
	    fprintf(stderr, "iogen%s:  Could not stat outpipe %s:  %s\n",
		    TagName, Outpipe, SYSERR);
	    exit(2);
	}
    } else {
	if (! S_ISFIFO(sbuf.st_mode)) {
	    fprintf(stderr,
		    "iogen%s:  Output file %s exists, but is not a FIFO\n",
		    TagName, Outpipe);
	    exit(2);
	}
    }

    if ((outfd = open(Outpipe, O_RDWR)) == -1) {
	fprintf(stderr,
		"iogen%s:  Couldn't open outpipe %s with flags O_RDWR: %s\n",
		TagName, Outpipe, SYSERR);
	exit(2);
    }

    return(outfd);
}


/*
 * Main io generation function.  form_iorequest() selects a system call to
 * do based on cmdline arguments, and proceeds to select parameters for that
 * system call.
 *
 * Returns 0 if req is filled in with a complete doio request, otherwise
 * returns -1.
 */

int
form_iorequest(req)
struct io_req   *req;
{
    int	    	    	mult, offset=0, length=0, slength;
    int	    	    	minlength, maxlength, laststart, lastend;
    int	    	    	minoffset, maxoffset;
    int			maxstride, nstrides;
    char    	    	pattern, *errp;
    struct strmap	*flags, *sc, *aio_strat;
    struct file_info	*fptr;
#ifdef CRAY
    int                 opcode, cmd;
#endif

    /*
     * Choose system call, flags, and file
     */

    sc = Syscall_List[random_range(0, Nsyscalls-1, 1, NULL)];
    req->r_type = sc->m_value;

#ifdef CRAY
    if (sc->m_value == LISTIO ) {
	    opcode = random_range(0, 1, 1, NULL) ? LO_READ : LO_WRITE;
	    cmd = random_range(0, 1, 1, NULL) ? LC_START : LC_WAIT;
    }
#endif

    if( sc->m_flags & SY_WRITE )
	    pattern = Byte_Patterns[random_range(0, sizeof(Byte_Patterns) - 1, 1, NULL)];
    else
	    pattern = 0;

#if CRAY
    /*
     * If sds io, simply choose a length (possibly pattern) and return
     */

    if (sc->m_flags & SY_SDS ) {
	    req->r_data.ssread.r_nbytes = random_range(Mintrans, Maxtrans, BSIZE, NULL);
	    if (sc->m_flags & SY_WRITE)
		    req->r_data.sswrite.r_pattern = pattern;

	    return 0;
    }
#endif

    /*
     * otherwise, we're doing file io.  Choose starting offset, length,
     * open flags, and possibly a pattern (for write/writea).
     */

    fptr = &File_List[random_range(0, Nfiles-1, 1, NULL)];
    flags = Flag_List[random_range(0, Nflags-1, 1, NULL)];

    /*
     * Choose offset/length multiple.  IO going to a device, or regular
     * IO that is O_RAW or O_SSD must be aligned on the file r_iou.  Otherwise
     * it must be aligned on the regular iou (normally 1).
     */

    if ( fptr->f_type == S_IFREG && (flags->m_flags & FLG_RAW) )
	    mult = fptr->f_riou;
    else
	    mult = fptr->f_iou;

    /*
     * Choose offset and length.  Both must be a multiple of mult
     */

    /*
     * Choose length first - it must be a multiple of mult
     */

    laststart = fptr->f_lastoffset;
    lastend = fptr->f_lastoffset + fptr->f_lastlength - 1;

    minlength = (Mintrans > mult) ? Mintrans : mult;

    switch (Offset_Mode->m_value) {
    case M_SEQUENTIAL:
	if (o_opt && lastend > laststart)
	    offset = random_range(laststart, lastend, 1, NULL);
	else
	    offset = lastend + 1;
 	if (offset && (offset%mult))
	    offset += mult - (offset % mult);

	if (minlength > fptr->f_length - offset)
	    offset = 0;

	maxlength = fptr->f_length - offset;
	if (maxlength > Maxtrans)
	    maxlength = Maxtrans;

	length = random_range(minlength, maxlength, mult, &errp);
	if (errp != NULL) {
	    fprintf(stderr, "iogen%s:  random_range(%d, %d, %d) failed\n",
		    TagName, minlength, maxlength, mult);
	    return -1;
	}

	break;

    case M_REVERSE:
	maxlength = laststart;

	if (maxlength > Maxtrans)
	    maxlength = Maxtrans;

	if (minlength > maxlength) {
	    laststart = fptr->f_length;
	    lastend = fptr->f_length;
	    maxlength = Maxtrans;
	}

	length = random_range(minlength, maxlength, mult, &errp);
	if (errp != NULL) {
	    fprintf(stderr, "iogen%s:  random_range(%d, %d, %d) failed\n",
		    TagName, minlength, maxlength, mult);
	    return -1;
	}

	offset = laststart - length;

	if (o_opt && lastend > laststart)
	    offset += random_range(1, lastend - laststart, 1, NULL);

	if (offset &&  (offset%mult))
	    offset -= offset % mult;

	break;
    
    case M_RANDOM:
	length = random_range(Mintrans, Maxtrans, mult, NULL);

	if (o_opt && lastend > laststart) {
		minoffset = laststart - length + 1;
		if (minoffset < 0) {
			minoffset = 0;
		}

		if (lastend + length > fptr->f_length) {
			maxoffset = fptr->f_length - length;
		} else {
			maxoffset = lastend;
		}
	} else {
	    minoffset = 0;
	    maxoffset = fptr->f_length - length;
	}

	if (minoffset < 0)
	    minoffset = 0;

	offset = random_range(minoffset, maxoffset, mult, &errp);
	if (errp != NULL) {
	    fprintf(stderr, "iogen%s:  random_range(%d, %d, %d) failed\n",
		    TagName, minoffset, maxoffset, mult);
	    return -1;
	}
    }

    fptr->f_lastoffset = offset;
    fptr->f_lastlength = length;

    /*
     * Choose an async io completion strategy if necessary
     */
    if( sc->m_flags & SY_ASYNC )
	    aio_strat = Aio_Strat_List[random_range(0, Naio_Strat_Types - 1,
						    1, NULL)];
    else
	    aio_strat = NULL;

    /*
     * fill in specific syscall record data
     */
    switch (sc->m_value) {
    case READ:
    case READA:
	strcpy(req->r_data.read.r_file, fptr->f_path);
	req->r_data.read.r_oflags = O_RDONLY | flags->m_value;
	req->r_data.read.r_offset = offset;
	req->r_data.read.r_nbytes = length;
	req->r_data.read.r_uflags = (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
	req->r_data.read.r_aio_strat = (aio_strat==NULL) ? 0 : aio_strat->m_value;
	req->r_data.read.r_nstrides = 1;
	req->r_data.read.r_nent = 1;
	break;

    case WRITE:
    case WRITEA:
	strcpy(req->r_data.write.r_file, fptr->f_path);
	req->r_data.write.r_oflags = O_WRONLY | flags->m_value;
	req->r_data.write.r_offset = offset;
	req->r_data.write.r_nbytes = length;
	req->r_data.write.r_pattern = pattern;
	req->r_data.write.r_uflags = (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
	req->r_data.write.r_aio_strat = (aio_strat==NULL) ? 0 : aio_strat->m_value;
	req->r_data.write.r_nstrides = 1;
	req->r_data.write.r_nent = 1;
	break;

    case READV:
    case AREAD:
    case PREAD:
    case WRITEV:
    case AWRITE:
    case PWRITE:

    case LREAD:
    case LREADA:
    case LWRITE:
    case LWRITEA:

    case RESVSP:
    case UNRESVSP:
    case DFFSYNC:
    case FSYNC2:
    case FDATASYNC:

	strcpy(req->r_data.io.r_file, fptr->f_path);
	req->r_data.io.r_oflags = ((sc->m_flags & SY_WRITE) ? O_WRONLY : O_RDONLY) | flags->m_value;
	req->r_data.io.r_offset = offset;
	req->r_data.io.r_nbytes = length;
	req->r_data.io.r_pattern = pattern;
	req->r_data.io.r_uflags = (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
	req->r_data.io.r_aio_strat = (aio_strat==NULL) ? 0 : aio_strat->m_value;
	req->r_data.io.r_nstrides = 1;
	req->r_data.io.r_nent = 1;
	break;

    case MMAPR:
    case MMAPW:
	strcpy(req->r_data.io.r_file, fptr->f_path);
	/* a subtle "feature" of mmap: a write-map requires
           the file open read/write */
	req->r_data.io.r_oflags = ((sc->m_flags & SY_WRITE) ? O_RDWR : O_RDONLY) | flags->m_value;
	req->r_data.io.r_offset = offset;
	req->r_data.io.r_nbytes = length;
	req->r_data.io.r_pattern = pattern;
	req->r_data.io.r_uflags = (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
	req->r_data.io.r_aio_strat = (aio_strat==NULL) ? 0 : aio_strat->m_value;
	req->r_data.io.r_nstrides = 1;
	req->r_data.io.r_nent = 1;
	break;

    case LSREAD:
    case LSREADA:
    case LEREAD:
    case LEREADA:
    case LSWRITE:
    case LSWRITEA:
    case LEWRITE:
    case LEWRITEA:
	/* multi-strided */
	strcpy(req->r_data.io.r_file, fptr->f_path);
	req->r_data.io.r_oflags = ((sc->m_flags & SY_WRITE) ? O_WRONLY : O_RDONLY) | flags->m_value;
	req->r_data.io.r_offset = offset;
	req->r_data.io.r_uflags = (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;
	req->r_data.io.r_aio_strat = (aio_strat==NULL) ? 0 : aio_strat->m_value;
	req->r_data.io.r_pattern = pattern;

	/* multi-strided request...
	 *  random number of strides (1...MaxStrides)
	 *  length of stride must be > minlength
	 *  length of stride must be % mult
	 *
	 * maxstrides = min(length / mult, overall.max#strides)
	 * nstrides = random #
	 * while( length / nstrides < minlength )
	 *	nstrides = new random #
	 */
	maxstride = length / mult;
	if(maxstride > Maxstrides)
	    maxstride = Maxstrides;

	if(!Minstrides)
		Minstrides=1;
	nstrides = random_range(Minstrides, maxstride, 1, &errp);
	if (errp != NULL) {
	    fprintf(stderr, "iogen%s:  random_range(%d, %d, %d) failed\n",
		    TagName, Minstrides, maxstride, 1);
	    return -1;
	}

	slength = length / nstrides;
	if(slength % mult != 0) {
	    if( mult > slength) {
		slength = mult;
	    } else {
		slength -= slength % mult;
	    }
	    nstrides = length / slength;
	    if(nstrides > Maxstrides)
		    nstrides = Maxstrides;
	}

	req->r_data.io.r_nbytes = slength;
	if( sc->m_flags & SY_NENT ) {
		req->r_data.io.r_nstrides = 1;
		req->r_data.io.r_nent = nstrides;
	} else {
		req->r_data.io.r_nstrides = nstrides;
		req->r_data.io.r_nent = 1;
	}
	break;

    case LISTIO:
#ifdef CRAY
	strcpy(req->r_data.listio.r_file, fptr->f_path);
	req->r_data.listio.r_offset = offset;
	req->r_data.listio.r_cmd = cmd;
	req->r_data.listio.r_aio_strat = (aio_strat==NULL) ? 0 : aio_strat->m_value;
	req->r_data.listio.r_filestride = 0;
	req->r_data.listio.r_memstride = 0;
	req->r_data.listio.r_opcode = opcode;
	req->r_data.listio.r_nstrides = 1;
	req->r_data.listio.r_nbytes = length;
	req->r_data.listio.r_uflags = (flags->m_flags & FLG_RAW) ? F_WORD_ALIGNED : 0;

	if (opcode == LO_WRITE) {
		req->r_data.listio.r_pattern = pattern;
		req->r_data.listio.r_oflags = O_WRONLY | flags->m_value;
	} else {
		req->r_data.listio.r_oflags = O_RDONLY | flags->m_value;
	}
#endif
	break;
    }

    return 0;
}

/*
 * Get information about a file that iogen uses to choose io length and
 * offset.  Information gathered is file length, iounit, and raw iounit.
 * For regurlar files, iounit is 1, and raw iounit is the iounit of the
 * device on which the file resides.  For block/character special files
 * the iounit and raw iounit are both the iounit of the device.
 *
 * Note:	buffered and osync io must be iounit aligned
 *		raw and ossd io must be raw iounit aligned
 */

int
get_file_info(rec)
struct file_info    *rec;
{
    struct stat			sbuf;
#ifdef CRAY
    struct lk_device_info	dinfo;
#endif
#ifdef sgi
    int				fd;
    struct dioattr		finfo;
#endif

    /*
     * Figure out if the files is regular, block or character special.  Any
     * other type is an error.
     */

    if (stat(rec->f_path, &sbuf) == -1) {
	fprintf(stderr, "iogen%s: get_file_info():  Could not stat() %s:  %s\n",
		TagName, rec->f_path, SYSERR);
	return -1;
    }

#if _CRAY2
    if ((! S_ISREG(sbuf.st_mode)) || strncmp(rec->f_path, "/dev/", 5) == 0) {
	fprintf(stderr, "iogen%s:  device level io not supported on cray2\n", TagName);
	return -1;
    }
#endif

    rec->f_type = sbuf.st_mode & S_IFMT;

    /*
     * If regular, iou is 1, and we must figure out the device on
     * which the file resides.  riou is the iou (logical sector size) of
     * this device.
     */

    if (S_ISREG(sbuf.st_mode)) {
	rec->f_iou = 1;
	rec->f_length = sbuf.st_size;

	/*
	 * If -r used, take Rawmult as the raw/ssd io multiple.  Otherwise
	 * attempt to determine it by looking at the device the file
	 * resides on.
	 */

	if (r_opt) {
	    rec->f_riou = Rawmult;
	    return 0;
	}

#ifdef CRAY
	if (lk_rawdev(rec->f_path, dinfo.path, sizeof(dinfo.path), 0) == -1)
	    return -1;

	if (lk_devinfo(&dinfo, 0) == -1) {
	    /* can't get raw I/O unit -- use stat to fudge it */
	    rec->f_riou = sbuf.st_blksize;
	} else {
	    rec->f_riou = ctob(dinfo.iou);
	}
#endif
#ifdef linux
	rec->f_riou = BSIZE;
#endif
#ifdef sgi
	if( (fd = open(rec->f_path, O_RDWR|O_DIRECT, 0)) != -1 ) {
	    if(fcntl(fd, F_DIOINFO, &finfo) != -1) {
		rec->f_riou = finfo.d_miniosz;
	    } else {
		fprintf(stderr,
			"iogen%s: Error %s (%d) getting direct I/O info of file %s\n",
			TagName, strerror(errno), errno, rec->f_path);
	    }
	    close(fd);
	} else {
	    rec->f_riou = BBSIZE;
	}
#endif	/* SGI */

    } else {

#ifdef CRAY
	/*
	 * Otherwise, file is a device.  Use lk_devinfo() to get its logical
	 * sector size.  This is the iou and riou
	 */

	strcpy(dinfo.path, rec->f_path);

	if (lk_devinfo(&dinfo, 0) == -1) {
	    fprintf(stderr, "iogen%s: %s:  %s\n", TagName, Lk_err_func, Lk_err_mesg);
	    return -1;
	}

	rec->f_iou = ctob(dinfo.iou);
	rec->f_riou = ctob(dinfo.iou);
	rec->f_length = ctob(dinfo.length);
#else
#ifdef sgi
	rec->f_riou = BBSIZE;
	rec->f_length = BBSIZE;
#else
	rec->f_riou = BSIZE;
	rec->f_length = BSIZE;
#endif /* sgi */
#endif /* CRAY */
    }

    return 0;
}

/*
 * Create file path as nbytes long.  If path exists, the file will either be
 * extended or truncated to be nbytes long.  Returns final size of file,
 * or -1 if there was a failure.
 */

int
create_file(path, nbytes)
char	*path;
int 	nbytes;
{
    int	    	fd, rval;
    char    	c;
    struct stat	sbuf;
#ifdef sgi
    int		nb;
    struct flock f;
    struct fsxattr xattr;
    struct dioattr finfo;
    char	*b, *buf;
#endif

    errno = 0;
    rval = stat(path, &sbuf);

    if (rval == -1) {
	if (errno == ENOENT) {
	    sbuf.st_size = 0;
	} else {
	    fprintf(stderr, "iogen%s:  Could not stat file %s:  %s (%d)\n",
		    TagName, path, SYSERR, errno);
	    return -1;
	}
    } else {
	if (! S_ISREG(sbuf.st_mode)) {
	    fprintf(stderr,
		    "iogen%s:  file %s exists, but is not a regular file - cannot modify length\n",
		    TagName, path);
	    return -1;
	}
    }

    if (sbuf.st_size == nbytes)
	return nbytes;

    Oflags |= O_CREAT | O_WRONLY;

    if ((fd = open(path, Oflags, 0666)) == -1) {
	fprintf(stderr, "iogen%s:  Could not create/open file %s: %s (%d)\n",
		TagName, path, SYSERR, errno);
	return -1;
    }

    /*
     * Truncate file if it is longer than nbytes, otherwise attempt to
     * pre-allocate file blocks.
     */

    if (sbuf.st_size > nbytes) {
	if (ftruncate(fd, nbytes) == -1) {
	    fprintf(stderr,
		    "iogen%s:  Could not ftruncate() %s to %d bytes:  %s (%d)\n",
		    TagName, path, nbytes, SYSERR, errno);
	    close(fd);
	    return -1;
	}
    } else {

#ifdef sgi
	/*
	 *  The file must be designated as Real-Time before any data
	 *  is allocated to it.
	 *
	 */
	if(Orealtime != 0) {
	    memset(&xattr, 0x00, sizeof(xattr));
	    xattr.fsx_xflags = XFS_XFLAG_REALTIME;
	    /*fprintf(stderr, "set: fsx_xflags = 0x%x\n", xattr.fsx_xflags);*/
	    if( fcntl(fd, F_FSSETXATTR, &xattr) == -1 ) {
		fprintf(stderr, "iogen%s: Error %s (%d) setting XFS XATTR->Realtime on file %s\n",
			TagName, SYSERR, errno, path);
		close(fd);
		return -1;
	    }

#ifdef DEBUG
	    if( fcntl(fd, F_FSGETXATTR, &xattr) == -1 ) {
		fprintf(stderr, "iogen%s: Error getting realtime flag %s (%d)\n",
			TagName, SYSERR, errno);
		close(fd);
		return -1;
	    } else {
		fprintf(stderr, "get: fsx_xflags = 0x%x\n", 
			xattr.fsx_xflags);
	    }
#endif
	}

	/*
	 * Reserve space with F_RESVSP
	 *
	 * Failure is ignored since F_RESVSP only works on XFS and the
	 * filesystem could be on EFS or NFS
	 */
	if( Oreserve ) {
	    f.l_whence = SEEK_SET;
	    f.l_start = 0;
	    f.l_len = nbytes;
	    
	    /*fprintf(stderr,
		    "create_file: fcntl(%d, F_RESVSP, { %d, %lld, %lld })\n",
		   fd, f.l_whence, (long long)f.l_start, (long long)f.l_len);*/

	    /* non-zeroing reservation */
	    if( fcntl( fd, F_RESVSP, &f ) == -1) {
		fprintf(stderr,
			"iogen%s:  Could not fcntl(F_RESVSP) %d bytes in file %s: %s (%d)\n",
			TagName, nbytes, path, SYSERR, errno);
		close(fd);
		return -1;
	    }
	}

	if( Oallocate ) {
	    /* F_ALLOCSP allocates from the start of the file to l_start */
	    f.l_whence = SEEK_SET;
	    f.l_start = nbytes;
	    f.l_len = 0;
	    /*fprintf(stderr,
		    "create_file: fcntl(%d, F_ALLOCSP, { %d, %lld, %lld })\n",
		    fd, f.l_whence, (long long)f.l_start,
		    (long long)f.l_len);*/

	    /* zeroing reservation */
	    if( fcntl( fd, F_ALLOCSP, &f ) == -1) {
		fprintf(stderr,
			"iogen%s:  Could not fcntl(F_ALLOCSP) %d bytes in file %s: %s (%d)\n",
			TagName, nbytes, path, SYSERR, errno);
		close(fd);
		return -1;
	    }
	}
#endif /* sgi */

	/*
	 * Write a byte at the end of file so that stat() sets the right
	 * file size.
	 */

#ifdef sgi
	if(Owrite == 2) {
	    close(fd);
	    if( (fd = open(path, O_CREAT|O_RDWR|O_DIRECT, 0)) != -1 ) {
		if(fcntl(fd, F_DIOINFO, &finfo) == -1) {
		    fprintf(stderr,
			    "iogen%s: Error %s (%d) getting direct I/O info for file %s\n",
			    TagName, SYSERR, errno, path);
		    return -1;
		} else {
		    /*fprintf(stderr, "%s: miniosz=%d\n", 
			    path, finfo.d_miniosz);*/
		}
	    } else {
		fprintf(stderr, "iogen%s: Error %s (%d) opening file %s with flags O_CREAT|O_RDWR|O_DIRECT\n",
			TagName, SYSERR, errno, path);
		return -1;
	    }

	    /*
	     * nb is nbytes adjusted down by an even d_miniosz block
	     *
	     * Note: the first adjustment can cause iogen to print a warning 
	     * 	about not being able to create a file of <nbytes> length, 
	     *	since the file will be shorter.
	     */
	    nb = nbytes-finfo.d_miniosz;
	    nb = nb-nb%finfo.d_miniosz;

	    /*fprintf(stderr,
		    "create_file_ow2: lseek(%d, %d {%d %d}, SEEK_SET)\n",
		    fd, nb, nbytes, finfo.d_miniosz);*/

	    if (lseek(fd, nb, SEEK_SET) == -1) {
		fprintf(stderr,
			"iogen%s:  Could not lseek() to EOF of file %s: %s (%d)\n\tactual offset %d file size goal %d miniosz %lld\n",
			TagName, path, SYSERR, errno,
			nb, nbytes, (long long)finfo.d_miniosz);
		close(fd);
		return -1;
	    }

	    b = buf = (char *)malloc(finfo.d_miniosz+finfo.d_mem);

	    if( ((long)buf % finfo.d_mem != 0) ) {
		buf += finfo.d_mem - ((long)buf % finfo.d_mem);
	    }
	    
	    memset(buf, 0, finfo.d_miniosz);

	    if ( (rval=write(fd, buf, finfo.d_miniosz)) != finfo.d_miniosz) {
		fprintf(stderr,
			"iogen%s:  Could not write %d byte length file %s: %s (%d)\n",
			TagName, nb, path, SYSERR, errno);
		fprintf(stderr,
			"\twrite(%d, 0x%lx, %d) = %d\n",
			fd, (long)buf, finfo.d_miniosz, rval);
		fprintf(stderr,
			"\toffset %d file size goal %d, miniosz=%d\n",
			nb, nbytes, finfo.d_miniosz);
		close(fd);
		return -1;
	    }
	    free(b);
	} else
#endif /* sgi */
	    if(Owrite) {
	    /*fprintf(stderr,
		    "create_file_Owrite: lseek(%d, %d {%d}, SEEK_SET)\n",
		    fd, nbytes-1, nbytes);*/

	    if (lseek(fd, nbytes-1, SEEK_SET) == -1) {
		fprintf(stderr,
			"iogen%s:  Could not lseek() to EOF in file %s:  %s (%d)\n\toffset goal %d\n",
			TagName, path, SYSERR, errno,
			nbytes-1);
		close(fd);
		return -1;
	    }

	    if ( (rval=write(fd, &c, 1)) != 1) {
		fprintf(stderr,
			"iogen%s:  Could not create a %d byte length file %s: %s (%d)\n",
			TagName, nbytes, path, SYSERR, errno);
		fprintf(stderr,
			"\twrite(%d, 0x%lx, %d) = %d\n",
			fd, (long)&c, 1, rval);
		fprintf(stderr,
			"\toffset %d file size goal %d\n",
			nbytes-1, nbytes);
		close(fd);
		return -1;
	    }
	}
    }

    fstat(fd, &sbuf);
    close(fd);

    return sbuf.st_size;
}

/*
 * Function to convert a string to its corresponding value in a strmap array.
 * If the string is not found in the array, the value corresponding to the
 * NULL string (the last element in the array) is returned.
 */

int
str_to_value(map, str)
struct strmap	*map;
char	    	*str;
{
    struct strmap   *mp;

    for (mp = map; mp->m_string != NULL; mp++)
	if (strcmp(mp->m_string, str) == 0)
	    break;

    return mp->m_value;
}
    
/*
 * Function to convert a string to its corresponding entry in a strmap array.
 * If the string is not found in the array, a NULL is returned.
 */

struct strmap *
str_lookup(map, str)
struct strmap	*map;
char	    	*str;
{
    struct strmap   *mp;

    for (mp = map; mp->m_string != NULL; mp++)
	if (strcmp(mp->m_string, str) == 0)
	    break;

    return((mp->m_string == NULL) ? NULL : mp);
}
    

/*
 * Function to convert a value to its corresponding string in a strmap array.
 * If the value is not found in the array, NULL is returned.
 */

char *
value_to_string(map, val)
struct strmap   *map;
int		val;
{
    struct strmap  *mp;

    for (mp = map; mp->m_string != NULL; mp++)
	if (mp->m_value == val)
	    break;

    return mp->m_string;
}

/*
 * Interpret cmdline options/arguments.  Exit with 1 if something on the
 * cmdline isn't kosher.
 */

int
parse_cmdline(argc, argv, opts)
int 	argc;
char	**argv;
char	*opts;
{
    int	    	    	o, len, nb, format_error;
    struct strmap	*flgs, *sc;
    char    	    	*file, *cp, ch;
    extern int	    	opterr;
    extern int	    	optind;
    extern char     	*optarg;
    struct strmap   	*mp;
    struct file_info	*fptr;
    int			nopenargs;
    char		*openargs[5];	/* Flags, cbits, cblks */
    char		*errmsg;
    int			str_to_int();
    opterr = 0;
#ifndef linux
    char		*ranges;
    struct strmap	*type;
#endif

    while ((o = getopt(argc, argv, opts)) != EOF) {
        switch ((char)o) {

 	case 'a':
#ifdef linux
	    fprintf(stderr, "iogen%s:  Unrecognized option -a on this platform\n", TagName);
	    exit(2);
#else
	    cp = strtok(optarg, ",");
	    while (cp != NULL) {
		if ((type = str_lookup(Aio_Strat_Map, cp)) == NULL) {
		    fprintf(stderr, "iogen%s:  Unrecognized aio completion strategy:  %s\n", TagName, cp);
		    exit(2);
		}

		Aio_Strat_List[Naio_Strat_Types++] = type;
		cp = strtok(NULL, ",");
	    }
	    a_opt++;
#endif
	    break;

 	case 'f':
	    cp = strtok(optarg, ",");
	    while (cp != NULL) {
		if( (flgs = str_lookup(Flag_Map, cp)) == NULL ) {
		    fprintf(stderr, "iogen%s:  Unrecognized flags:  %s\n", TagName, cp);
		    exit(2);
		}

		cp = strtok(NULL, ",");

#ifdef O_SSD
		if (flgs->m_value & O_SSD && ! Sds_Avail) {
		    fprintf(stderr, "iogen%s:  Warning - no sds available, ignoring ssd flag\n", TagName);
		    continue;
		}
#endif

		Flag_List[Nflags++] = flgs;
	    }
	    f_opt++;
	    break;

	case 'h':
	    help(stdout);
	    exit(0);
	    break;

	case 'i':
	    format_error = 0;

	    switch (sscanf(optarg, "%i%c", &Iterations, &ch)) {
	    case 1:
		Time_Mode = 0;
		break;

	    case 2:
		if (ch == 's')
		    Time_Mode = 1;
		else
		    format_error = 1;
		break;

	    default:
		format_error = 1;
	    }

	    if (Iterations < 0)
		format_error = 1;

	    if (format_error) {
		fprintf(stderr, "iogen%s:  Illegal -i arg (%s):  Must be of the format:  number[s]\n", TagName, optarg);
		fprintf(stderr, "        where 'number' is >= 0\n");
		exit(1);
	    }

	    i_opt++;
	    break;

	case 'L':
#ifdef linux
	    fprintf(stderr, "iogen%s:  Unrecognized option -L on this platform\n", TagName);
	    exit(2);
#else
	    if( parse_ranges(optarg, 1, 255, 1, NULL, &ranges, 
			     &errmsg ) == -1 ) {
		    fprintf(stderr, "iogen%s: error parsing listio range '%s': %s\n",
			    TagName, optarg, errmsg);
		    exit(1);
	    }

	    Minstrides = range_min(ranges, 0);
	    Maxstrides = range_max(ranges, 0);

	    free(ranges);
	    L_opt++;
#endif
	    break;

	case 'm':
	    if ((Offset_Mode = str_lookup(Omode_Map, optarg)) == NULL) {
		fprintf(stderr, "iogen%s:  Illegal -m arg (%s)\n", TagName, optarg);
		exit(1);
	    }

	    m_opt++;
	    break;

	case 'N':
	    sprintf( TagName, "(%.39s)", optarg );
	    break;

	case 'o':
	    o_opt++;
	    break;

	case 'O':

	    nopenargs = string_to_tokens(optarg, openargs, 4, ":/");

#ifdef CRAY
	    if(nopenargs)
		sscanf(openargs[1],"%i", &Ocbits);
	    if(nopenargs > 1)
		sscanf(openargs[2],"%i", &Ocblks);

	    Oflags = parse_open_flags(openargs[0], &errmsg);
	    if(Oflags == -1) {
		fprintf(stderr, "iogen%s: -O %s error: %s\n", TagName, optarg, errmsg);
		exit(1);
	    }
#endif
#ifdef linux
	    Oflags = parse_open_flags(openargs[0], &errmsg);
	    if(Oflags == -1) {
		fprintf(stderr, "iogen%s: -O %s error: %s\n", TagName, optarg, errmsg);
		exit(1);
	    }
#endif
#ifdef sgi
	    if(!strcmp(openargs[0], "realtime")) {
		/*
		 * -O realtime:extsize
		 */
		Orealtime = 1;
		if(nopenargs > 1)
		    sscanf(openargs[1],"%i", &Oextsize);
		else
		    Oextsize=0;
	    } else if( !strcmp(openargs[0], "allocate") ||
		       !strcmp(openargs[0], "allocsp")) {
		/*
		 * -O allocate
		 */
		Oreserve=0;
		Oallocate=1;
	    } else if(!strcmp(openargs[0], "reserve")) {
		/*
		 * -O [no]reserve
		 */
		Oallocate=0;
		Oreserve=1;
	    } else if(!strcmp(openargs[0], "noreserve")) {
		/* Oreserve=1 by default; this clears that default */
		Oreserve=0;
	    } else if(!strcmp(openargs[0], "nowrite")) {
		/* Owrite=1 by default; this clears that default */
		Owrite=0;
	    } else if(!strcmp(openargs[0], "direct")) {
		/* this means "use direct i/o to preallocate file" */
		Owrite=2;
	    } else {
		fprintf(stderr, "iogen%s: Error: -O %s error: unrecognized option\n",
			TagName, openargs[0]);
		exit(1);
	    }
#endif

	    O_opt++;
	    break;

	case 'p':
	    Outpipe = optarg;
	    p_opt++;
	    break;

	case 'r':
	    if ((Rawmult = str_to_bytes(optarg)) == -1 ||
		          Rawmult < 11 || Rawmult % BSIZE) {
		fprintf(stderr, "iogen%s:  Illegal -r arg (%s).  Must be > 0 and multipe of BSIZE (%d)\n",
			TagName, optarg, BSIZE);
		exit(1);
	    }

	    r_opt++;
	    break;

 	case 's':
	    cp = strtok(optarg, ",");
	    while (cp != NULL) {
		if ((sc = str_lookup(Syscall_Map, cp)) == NULL) {
		    fprintf(stderr, "iogen%s:  Unrecognized syscall:  %s\n", TagName, cp);
		    exit(2);
		}

		do {
		    /* >>> sc->m_flags & FLG_SDS */
		    if (sc->m_value != SSREAD && sc->m_value != SSWRITE)
			Fileio++;

		    Syscall_List[Nsyscalls++] = sc;
		} while ( (sc = str_lookup(++sc, cp)) != NULL);

		cp = strtok(NULL, ",");
	    }
	    s_opt++;
	    break;

	case 't':
	    if ((Mintrans = str_to_bytes(optarg)) == -1) {
		fprintf(stderr, "iogen%s:  Illegal -t arg (%s):  Must have the form num[bkm]\n", TagName, optarg);
		exit(1);
	    }
	    t_opt++;
	    break;

	case 'T':
	    if ((Maxtrans = str_to_bytes(optarg)) == -1) {
		fprintf(stderr, "iogen%s:  Illegal -T arg (%s):  Must have the form num[bkm]\n", TagName, optarg);
		exit(1);
	    }
	    T_opt++;
	    break;

	case 'q':
	    q_opt++;
	    break;

	case '?':
	    usage(stderr);
	    exit(1);
	}
    }

    /*
     * Supply defaults
     */

    if( ! L_opt ) {
	    Minstrides = 1;
	    Maxstrides = 255;
    }

    if (! m_opt)
	Offset_Mode = str_lookup(Omode_Map, "sequential");

    if (! i_opt)
	Iterations = 0;

    if (! t_opt)
	Mintrans = 1;

    if (! T_opt)
	Maxtrans = 256 * BSIZE;

    if( ! O_opt) 
	Oflags = Ocbits = Ocblks = 0;

    /*
     * Supply default async io completion strategy types.
     */

    if (! a_opt) {
	    for (mp = Aio_Strat_Map; mp->m_string != NULL; mp++) {
		    Aio_Strat_List[Naio_Strat_Types++] = mp;
	    }
    }

    /*
     * Supply default syscalls.  Default is read,write,reada,writea,listio.
     */

    if (! s_opt) {
	Nsyscalls = 0;
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "read");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "write");
#ifdef CRAY
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "reada");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "writea");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lread");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lreada");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lwrite");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "lwritea");
#endif

#ifdef sgi
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "pread");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "pwrite");
	/*Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "aread");*/
	/*Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "awrite");*/
#endif

#ifndef CRAY
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "readv");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "writev");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "mmread");
	Syscall_List[Nsyscalls++] = str_lookup(Syscall_Map, "mmwrite");
#endif

        Fileio = 1;
    }

    if (Fileio && (argc - optind < 1)) {
	fprintf(stderr, "iogen%s:  No files specified on the cmdline\n", TagName);
	exit(1);
    }

    /*
     * Supply default file io flags - defaut is 'buffered,raw,sync,ldraw'.
     */

    if (! f_opt && Fileio) {
	    Nflags = 0;
	    Flag_List[Nflags++] = str_lookup(Flag_Map, "buffered");
	    Flag_List[Nflags++] = str_lookup(Flag_Map, "sync");
#ifdef CRAY
	    Flag_List[Nflags++] = str_lookup(Flag_Map, "raw+wf");
    	    Flag_List[Nflags++] = str_lookup(Flag_Map, "ldraw");
#endif

#ifdef sgi
	    /* Warning: cannot mix direct i/o with others! */
	    Flag_List[Nflags++] = str_lookup(Flag_Map, "dsync");
	    Flag_List[Nflags++] = str_lookup(Flag_Map, "rsync");
	    /* Flag_List[Nflags++] = str_lookup(Flag_Map, "rsync+sync");*/
	    /* Flag_List[Nflags++] = str_lookup(Flag_Map, "rsync+dsync");*/
#endif
    }

    if (Fileio) {
	if (optind >= argc) {
	    fprintf(stderr, "iogen%s:  No files listed on the cmdline\n", TagName);
	    exit(1);
	}

	/*
	 * Initialize File_List[] - only necessary if doing file io.  First 
	 * space for the File_List array, then fill it in.
	 */
	
	File_List = (struct file_info *)
	    malloc((argc-optind) * sizeof(struct file_info));
	
	if (File_List == NULL) {
	    fprintf(stderr, "iogen%s:  Could not malloc space for %d file_info structures\n", TagName, argc-optind);
	    exit(2);
	}

	memset(File_List, 0, (argc-optind) * sizeof(struct file_info));

        Nfiles = 0;
        while (optind < argc) {
   	    len = -1;

	    /*
	     * Pick off leading len: if it's there and create/extend/trunc
	     * the file to the desired length.  Otherwise, just make sure
	     * the file is accessable.
	     */

	    if ((cp = strchr(argv[optind], ':')) != NULL) {
	        *cp = '\0';
	        if ((len = str_to_bytes(argv[optind])) == -1) {
		    fprintf(stderr,
			    "iogen%s:  illegal file length (%s) for file %s\n",
			    TagName, argv[optind], cp+1);
		    exit(2);
	        }
	        *cp = ':';
	        file = cp+1;

		if (strlen(file) > MAX_FNAME_LENGTH) {
		    fprintf(stderr, "iogen%s:  Max fname length is %d chars - ignoring file %s\n",
			    TagName, MAX_FNAME_LENGTH, file);
		    optind++;
		    continue;
		}

		nb = create_file(file, len);

		if (nb < len) {
		    fprintf(stderr,
			    "iogen%s warning:  Couldn't create file %s of %d bytes\n",
			    TagName, file, len);

		    if (nb <= 0) {
			optind++;
			continue;
		    }
		}
	    } else {
	        file = argv[optind];
	        if (access(file, R_OK | W_OK) == -1) {
	   	    fprintf(stderr, "iogen%s:  file %s cannot be accessed for reading and/or writing:  %s (%d)\n",
			    TagName, file, SYSERR, errno);
		    exit(2);
	        }
	    }

	    /*
	     * get per-file information
	     */

	    fptr = &File_List[Nfiles];

	    if (file[0] == '/') {
	        strcpy(fptr->f_path, file);
	    } else {
	        if (getcwd(fptr->f_path, sizeof(fptr->f_path)-1) == NULL)
			perror("Could not get current working directory");
	        strcat(fptr->f_path, "/");
	        strcat(fptr->f_path, file);
	    }

	    if (get_file_info(fptr) == -1) {
	        fprintf(stderr, "iogen%s warning:  Error getting file info for %s\n", TagName, file);
	    } else {

		/*
		 * If the file length is smaller than our min transfer size,
		 * ignore it.
		 */

		if (fptr->f_length < Mintrans) {
		    fprintf(stderr, "iogen%s warning:  Ignoring file %s\n",
			    TagName, fptr->f_path);
		    fprintf(stderr, "                length (%d) is < min transfer size (%d)\n",
			    fptr->f_length, Mintrans);
		    optind++;
		    continue;
		}

                /*
                 * If the file length is smaller than our max transfer size,
                 * ignore it.
                 */

                if (fptr->f_length < Maxtrans) {
                    fprintf(stderr, "iogen%s warning:  Ignoring file %s\n",
                            TagName, fptr->f_path);
                    fprintf(stderr, "                length (%d) is < max transfer size (%d)\n",
                            fptr->f_length, Maxtrans);
                    optind++;
                    continue;
                }

		if (fptr->f_length > 0) {
		    switch (Offset_Mode->m_value) {
		    case M_SEQUENTIAL:
			fptr->f_lastoffset = 0;
			fptr->f_lastlength = 0;
			break;
			
		    case M_REVERSE:
			fptr->f_lastoffset = fptr->f_length;
			fptr->f_lastlength = 0;
			break;
			
		    case M_RANDOM:
			fptr->f_lastoffset = fptr->f_length / 2;
			fptr->f_lastlength = 0;
			break;
		    }
		    
		    Nfiles++;
		}
	    }

 	    optind++;
	}

        if (Nfiles == 0) {
	    fprintf(stderr, "iogen%s:  Could not create, or gather info for any test files\n", TagName);
	    exit(2);
        }
    }

    return 0;
}

int
help(stream)
FILE	*stream;
{
    usage(stream);
    fprintf(stream, "\n");
#ifndef linux
    fprintf(stream, "\t-a aio_type,...  Async io completion types to choose.  Supported types\n");
#ifdef CRAY
#if _UMK || RELEASE_LEVEL >= 8000
    fprintf(stream, "\t                 are:  poll, signal, recall, recalla, and recalls.\n");
#else
    fprintf(stream, "\t                 are:  poll, signal, recalla, and recalls.\n");
#endif
#else
    fprintf(stream, "\t                 are:  poll, signal, suspend, and callback.\n");
#endif
    fprintf(stream, "\t                 Default is all of the above.\n");
#else /* !linux */
    fprintf(stream, "\t-a               (Not used on Linux).\n");
#endif /* !linux */
    fprintf(stream, "\t-f flag,...      Flags to use for file IO.  Supported flags are\n");
#ifdef CRAY
    fprintf(stream, "\t                 raw, ssd, buffered, ldraw, sync,\n");
    fprintf(stream, "\t                 raw+wf, raw+wf+ldraw, raw+wf+ldraw+sync,\n");
    fprintf(stream, "\t                 and parallel (unicos/mk on MPP only).\n");
    fprintf(stream, "\t                 Default is 'raw,ldraw,sync,buffered'.\n");
#else
#ifdef sgi
    fprintf(stream, "\t                 buffered, direct, sync, dsync, rsync,\n");
    fprintf(stream, "\t                 rsync+dsync.\n");
    fprintf(stream, "\t                 Default is 'buffered,sync,dsync,rsync'.\n");
#else
    fprintf(stream, "\t                 buffered, sync.\n");
    fprintf(stream, "\t                 Default is 'buffered,sync'.\n");
#endif /* sgi */
#endif /* CRAY */
    fprintf(stream, "\t-h               This help.\n");
    fprintf(stream, "\t-i iterations[s] # of requests to generate.  0 means causes iogen\n");
    fprintf(stream, "\t                 to run until it's killed.  If iterations is suffixed\n");
    fprintf(stream, "\t                 with 's', then iterations is the number of seconds\n");
    fprintf(stream, "\t                 that iogen should run for.  Default is '0'.\n");
#ifndef linux
    fprintf(stream, "\t-L min:max       listio nstrides / nrequests range\n");
#else
    fprintf(stream, "\t-L               (Not used on Linux).\n");
#endif /* !linux */
    fprintf(stream, "\t-m offset-mode   The mode by which iogen chooses the offset for\n");
    fprintf(stream, "\t                 consectutive transfers within a given file.\n");
    fprintf(stream, "\t                 Allowed values are 'random', 'sequential',\n");
    fprintf(stream, "\t                 and 'reverse'.\n");
    fprintf(stream, "\t                 sequential is the default.\n");
    fprintf(stream, "\t-N tagname       Tag name, for Monster.\n");
    fprintf(stream, "\t-o               Form overlapping consecutive requests.\n");
    fprintf(stream, "\t-O               Open flags for creating files\n");
#ifdef CRAY
    fprintf(stream, "\t                 {O_PLACE,O_BIG,etc}[:CBITS[:CBLKS]]\n");
#endif
#ifdef sgi
    fprintf(stream, "\t                 realtime:extsize - put file on real-time volume\n");
    fprintf(stream, "\t                 allocate - allocate space with F_ALLOCSP\n");
    fprintf(stream, "\t                 reserve - reserve space with F_RESVSP (default)\n");
    fprintf(stream, "\t                 noreserve - do not reserve with F_RESVSP\n");
    fprintf(stream, "\t                 direct - use O_DIRECT I/O to write to the file\n");
#endif
#ifdef linux
    fprintf(stream, "\t                 {O_SYNC,etc}\n");
#endif
    fprintf(stream, "\t-p               Output pipe.  Default is stdout.\n");
    fprintf(stream, "\t-q               Quiet mode.  Normally iogen spits out info\n");
    fprintf(stream, "\t                 about test files, options, etc. before starting.\n");
    fprintf(stream, "\t-s syscall,...   Syscalls to do.  Supported syscalls are\n");
#ifdef sgi
    fprintf(stream, "\t                 read, write, pread, pwrite, readv, writev\n");
    fprintf(stream, "\t                 aread, awrite, resvsp, unresvsp, ffsync,\n");
    fprintf(stream, "\t                 mmread, mmwrite, fsync2, fdatasync,\n");
    fprintf(stream, "\t                 Default is 'read,write,pread,pwrite,readv,writev,mmread,mmwrite'.\n");
#endif
#ifdef CRAY
    fprintf(stream, "\t                 read, write, reada, writea, listio,\n");
    fprintf(stream, "\t                 ssread (PVP only), and sswrite (PVP only).\n");
    fprintf(stream, "\t                 Default is 'read,write,reada,writea,listio'.\n");
#endif
#ifdef linux
    fprintf(stream, "\t                 read, write, readv, writev,\n");
    fprintf(stream, "\t                 mmread, mmwrite, fsync2, fdatasync,\n");
    fprintf(stream, "\t                 Default is 'read,write,readv,writev,mmread,mmwrite'.\n");
#endif
    fprintf(stream, "\t-t mintrans      Min transfer length\n");
    fprintf(stream, "\t-T maxtrans      Max transfer length\n");
    fprintf(stream, "\n");
    fprintf(stream, "\t[len:]file,...   Test files to do IO against (note ssread/sswrite\n");
    fprintf(stream, "\t                 don't need a test file).  The len: syntax\n");
    fprintf(stream, "\t                 informs iogen to first create/expand/truncate the\n");
    fprintf(stream, "\t                 to the desired length.\n");
    fprintf(stream, "\n");
    fprintf(stream, "\tNote:  The ssd flag causes sds transfers to also be done.\n");
    fprintf(stream, "\t       To totally eliminate sds transfers, you must eleminate sds\n");
    fprintf(stream, "\t       from the flags (-f) and ssread,ssrite from the syscalls (-s)\n");
    fprintf(stream, "\tThe mintrans, maxtrans, and len: parameters are numbers of the\n");
    fprintf(stream, "\tform [0-9]+[bkm].  The optional trailing b, k, or m multiplies\n");
    fprintf(stream, "\tthe number by blocks, kilobytes, or megabytes.  If no trailing\n");
    fprintf(stream, "\tmultiplier is present, the number is interpreted as bytes\n");

    return 0;
}

/*
 * Obvious - usage clause
 */

int
usage(stream)
FILE	*stream;
{
    fprintf(stream, "usage%s:  iogen [-hoq] [-a aio_type,...] [-f flag[,flag...]] [-i iterations] [-p outpipe] [-m offset-mode] [-s syscall[,syscall...]] [-t mintrans] [-T maxtrans] [ -O file-create-flags ] [[len:]file ...]\n", TagName);
    return 0;
}
